﻿using System;
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;
#else
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Utilities;
#if !DOT_NET
using Windows.System.Threading;
using Windows.UI.Core;

#endif

namespace StyleMVVM.Messenger
{
	/// <summary>
	/// This class provides the default implementation for IDispatchedMessenger. 
	/// It also includes an IDispatchedMessenger singleton instance that is resolved using the IoC container
	/// </summary>
	public sealed class DispatchedMessenger : IDispatchedMessenger
	{
		private static readonly object lockObject = new object();
		private static volatile WeakReference instance;
		private readonly ConcurrentDictionary<Type, List<IMessageHandlerWrapper>> handlers;
		private static readonly string supplemental = typeof(DispatchedMessenger).FullName;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(DispatchedMessenger)).
			          As(typeof(IDispatchedMessenger)).
			          CoreExport(CoreExportStartOrder.First).
			          AndSharedPermenantly();
		}

		public DispatchedMessenger()
		{
			handlers = new ConcurrentDictionary<Type, List<IMessageHandlerWrapper>>();
		}

		/// <summary>
		/// This the default implementation for IDispatchedMessenger resolved using the IoC container
		/// </summary>
		public static IDispatchedMessenger Instance
		{
			get
			{
				IDispatchedMessenger messenger = null;

				if (instance != null)
				{
					messenger = instance.Target as IDispatchedMessenger;
				}

				if (messenger == null)
				{
					lock (lockObject)
					{
						if (instance == null || instance.Target == null)
						{
							instance = new WeakReference(
								Bootstrapper.Instance.Container.LocateByType(typeof(IDispatchedMessenger)));
						}

						messenger = instance.Target as IDispatchedMessenger;
					}
				}

				return messenger;
			}
		}

		public void Send(object message)
		{
			Type messageType = message.GetType();
			List<IMessageHandlerWrapper> handlerList;

			if (handlers.TryGetValue(messageType, out handlerList))
			{
				List<IMessageHandlerWrapper> deleteList = null;

				foreach (IMessageHandlerWrapper o in handlerList)
				{
					try
					{
						if (!o.Execute(message))
						{
							if (deleteList == null)
							{
								deleteList = new List<IMessageHandlerWrapper>();
							}

							deleteList.Add(o);
						}
					}
					catch (Exception exp)
					{
						if (deleteList == null)
						{
							deleteList = new List<IMessageHandlerWrapper>();
						}

						deleteList.Add(o);

						Logger.Error("Exception was thrown while calling message handler", supplemental, exp);
					}
				}

				if (deleteList != null)
				{
					handlers.AddOrUpdate(messageType,
					                     new List<IMessageHandlerWrapper>(),
					                     (x, y) =>
						                     {
							                     List<IMessageHandlerWrapper> newWrappers = new List<IMessageHandlerWrapper>(y);

							                     foreach (IMessageHandlerWrapper messageHandlerWrapper in y)
							                     {
								                     if (deleteList.Count == 0 ||
								                         !deleteList.Remove(messageHandlerWrapper))
								                     {
									                     newWrappers.Add(messageHandlerWrapper);
								                     }
							                     }

							                     return newWrappers;
						                     });
				}
			}
		}

		public void Register(object callback)
		{
			Register(callback, false, false);
		}

		public void Register(object callback, bool background, bool holdReference)
		{
			if (!(callback is Delegate))
			{
				throw new ArgumentException("callback must be of type delegate with one parameter");
			}

			Delegate callbackDelegate = callback as Delegate;

			MethodInfo methInfo = callbackDelegate.GetMethodInfo();
			ParameterInfo[] parameterInfos = methInfo.GetParameters();

			if (parameterInfos.Length != 1)
			{
				throw new ArgumentException("callback must be of type delegate with one parameter");
			}

			IMessageHandlerWrapper newMessageHander = null;

			if (holdReference)
			{
				newMessageHander = new ReferenceMessageHandlerWrapper(
					callbackDelegate, background);
			}
			else
			{
				newMessageHander =
					new WeakMessageHandlerWrapper(callbackDelegate, background);
			}

			handlers.AddOrUpdate(parameterInfos[0].ParameterType,
			                     (x) => new List<IMessageHandlerWrapper> { newMessageHander },
			                     (x, y) =>
				                     {
					                     List<IMessageHandlerWrapper> newList = new List<IMessageHandlerWrapper>(y);

					                     newList.Add(newMessageHander);

					                     return newList;
				                     });
		}

		public void RegisterMethod(object target, string methodName, bool background, bool holdReference)
		{
			MethodInfo methodInfo = null;
			ParameterInfo parameterInfo = null;

			foreach (MethodInfo targetMethod in target.GetType().GetTypeInfo().GetDeclaredMethods(methodName))
			{
				ParameterInfo[] paramInfos = targetMethod.GetParameters();

				if (paramInfos.Length == 1)
				{
					methodInfo = targetMethod;
					parameterInfo = paramInfos[0];

					break;
				}
			}

			if (methodInfo != null)
			{
				IMessageHandlerWrapper newMessageHander = null;

				if (holdReference)
				{
					Type delegateType = typeof(Action<>).MakeGenericType(methodInfo.GetParameters()[0].ParameterType);

					newMessageHander = new ReferenceMessageHandlerWrapper(
						methodInfo.CreateDelegate(delegateType, target), background);
				}
				else
				{
					newMessageHander =
						new WeakMessageHandlerWrapper(target, methodInfo, background);
				}

				handlers.AddOrUpdate(parameterInfo.ParameterType,
				                     (x) => new List<IMessageHandlerWrapper> { newMessageHander },
				                     (x, y) =>
					                     {
						                     List<IMessageHandlerWrapper> newList = new List<IMessageHandlerWrapper>(y);

						                     newList.Add(newMessageHander);

						                     return newList;
					                     });
			}
		}

		public void RegisterJSCallback(TypeWrapper messageType, JSCallback callback, bool background)
		{
			handlers.AddOrUpdate(messageType.Type,
			                     (x) => new List<IMessageHandlerWrapper>
				                            {
					                            new ReferenceMessageHandlerWrapper(callback, background)
				                            },
			                     (x, y) =>
				                     {
					                     List<IMessageHandlerWrapper> newList = new List<IMessageHandlerWrapper>(y);

					                     newList.Add(new ReferenceMessageHandlerWrapper(callback, background));

					                     return newList;
				                     });
		}

		public void Unregister(object callback)
		{
			if (!(callback is Delegate))
			{
				throw new ArgumentException("callback must be of type delegate with one parameter");
			}

			Delegate callbackDelegate = callback as Delegate;

			MethodInfo methInfo = callbackDelegate.GetMethodInfo();
			ParameterInfo[] parameterInfos = methInfo.GetParameters();

			if (parameterInfos.Length != 1)
			{
				throw new ArgumentException("callback must be of type delegate with one parameter");
			}

			Type typeT = parameterInfos[0].ParameterType;

			handlers.AddOrUpdate(typeT,
			                     new List<IMessageHandlerWrapper>(),
			                     (x, y) =>
				                     {
					                     List<IMessageHandlerWrapper> newWrappers = new List<IMessageHandlerWrapper>();

					                     newWrappers.RemoveAll(z => Equals(z.Method, methInfo) && z.Target == callbackDelegate.Target);

					                     return newWrappers;
				                     });
		}

		public void RegisterObjectHandlers(object handlerObject)
		{
			foreach (MethodInfo method in handlerObject.GetType().GetTypeInfo().DeclaredMethods)
			{
				if (method.IsPublic && !method.IsStatic && method.GetParameters().Length == 1)
				{
					bool? background = null;

					foreach (Attribute customAttr in method.GetCustomAttributes())
					{
						if (customAttr is IMessageHandlerAttribute)
						{
							background = false;
							break;
						}

						if (customAttr is IBackgroundMessageHandlerAttribute)
						{
							background = true;
							break;
						}
					}

					if (background.HasValue)
					{
						Type actionType = typeof(Action<>).MakeGenericType(method.GetParameters()[0].ParameterType);

						Delegate handlerMethod = method.CreateDelegate(actionType, handlerObject);

						Register(handlerMethod, background.Value, false);
					}
				}
			}
		}

		public void UnregisterObjectHandlers(object handlerObject)
		{
			foreach (MethodInfo method in handlerObject.GetType().GetTypeInfo().DeclaredMethods)
			{
				if (method.IsPublic && !method.IsStatic && method.GetParameters().Length == 1)
				{
					foreach (Attribute customAttr in method.GetCustomAttributes())
					{
						if (customAttr is IMessageHandlerAttribute || customAttr is IBackgroundMessageHandlerAttribute)
						{
							Type actionType = typeof(Action<>).MakeGenericType(method.GetParameters()[0].ParameterType);

							Delegate handlerMethod = method.CreateDelegate(actionType, handlerObject);

							Unregister(handlerMethod);
							break;
						}
					}
				}
			}
		}
	}
}